package kvoog.arkanoid.common;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.artemis.Component;
import com.artemis.Entity;

/**
 * This is a state machine for an entity. The state machine manages a set of states,
 * each of which has a set of component providers. When the state machine changes the state, it removes
 * components associated with the previous state and adds components associated with the new state.
 * 
 * Ported from Ash entity component system framework for Action Script by Igor Knezevic. 
 * http://www.richardlord.net/blog/finite-state-machines-with-ash
 */
public class EntityStateMachine {

	private Map<String, EntityState> states;
	private EntityState currentState;
	private Entity entity;

	/**
	 * Constructor. Creates an EntityStateMachine.
	 */
	public EntityStateMachine(Entity entity) {
		this.entity = entity;
		states = new HashMap<String, EntityState>();
	}

	/**
	 * Getter for parent entity of this state machine.
	 * 
	 * @return parent entity
	 */
	public Entity getEntity() {
		return entity;
	}
	
	/**
	 * Add a state to this state machine.
	 *
	 * @param name The name of this state - used to identify it later in the changeState method call.
	 * @param state The state.
	 * @return This state machine, so methods can be chained.
	 */
	public EntityStateMachine addState(String name, EntityState state) {
		states.put(name, state);
		return this;
	}
	
	/**
	 * Create a new state in this state machine.
	 *
	 * @param name The name of the new state - used to identify it later in the changeState method call.
	 * @return The new EntityState object that is the state. This will need to be configured with
	 * the appropriate component providers.
	 */
	public EntityState createState(String name) {
		EntityState state = new EntityState();
		states.put(name, state);
		return state;
	}
	
	/**
     * Change to a new state. The components from the old state will be removed and the components
     * for the new state will be added.
     *
     * @param name The name of the state to change to.
     */
	public void changeState(String name) {
		
		EntityState newState = states.get(name);
		
		if (newState == null) {
			throw new RuntimeException("Entity state " + name + " doesm't exist");
		}
		
		if (newState == currentState) {
			return;
		}
		
		Map<Class<? extends Component>, IComponentProvider> toAdd;
		
		if (currentState != null) {
			
			toAdd = new HashMap<Class<? extends Component>, IComponentProvider>();
			Iterator<Class<? extends Component>> it = newState.providers.keySet().iterator();
			while (it.hasNext()) {
				Class<? extends Component> type = it.next();
				toAdd.put(type, newState.providers.get(type));
			}
			
			it = currentState.providers.keySet().iterator();
			while (it.hasNext()) {
				Class<? extends Component> type = it.next();
				// TODO Ignoring the same component is not yet implemented
				//IComponentProvider other = toAdd.get(type);
				//if ((other != null) && (other.getId() == currentState.providers.get(type).getId())) {
				//	toAdd.remove(type);
				//}
				//else {
				
				// changedInWorld must be called for Artemis to refresh affected systems
				entity.removeComponent(type).changedInWorld();
				
				//}					
			}
		}
		else {
			toAdd = newState.providers;
		}
		
		Iterator<Class<? extends Component>> it = toAdd.keySet().iterator();
		while (it.hasNext()) {
			Class<? extends Component> type = it.next();
			// changedInWorld must be called for Artemis to refresh affected systems
			entity.addComponent(toAdd.get(type).getComponent()).changedInWorld();
		}
		
		currentState = newState;
	}
}
